﻿using System;
using System.Collections.Generic;
using System.ComponentModel.Design;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Windows.Forms;
using Microsoft.TeamFoundation.Client;
using Microsoft.TeamFoundation.Common;
using Microsoft.TeamFoundation.VersionControl.Client;
using Microsoft.TeamFoundation.WorkItemTracking.Client;
using Microsoft.VisualStudio;
using Microsoft.VisualStudio.Shell;
using Microsoft.VisualStudio.Shell.Interop;
using Microsoft.VisualStudio.TeamFoundation;
using Microsoft.VisualStudio.TeamFoundation.VersionControl;
using Microsoft.VisualStudio.TeamFoundation.WorkItemTracking;
using System.Threading;

namespace TFSExp.ExtendedMerge
{
    /// <summary>
    /// This is the class that implements the package exposed by this assembly.
    ///
    /// The minimum requirement for a class to be considered a valid package for Visual Studio
    /// is to implement the IVsPackage interface and register itself with the shell.
    /// This package uses the helper classes defined inside the Managed Package Framework (MPF)
    /// to do it: it derives from the Package class that provides the implementation of the 
    /// IVsPackage interface and uses the registration attributes defined in the framework to 
    /// register itself and its components with the shell.
    /// </summary>
    [PackageRegistration(UseManagedResourcesOnly = true)]
    [InstalledProductRegistration("#110", "#112", "1.1", IconResourceID = 400)]
    [ProvideLoadKey("Standard", "1.1", "TFS Productivity Tools - Extended Merge 2012", "Mark Babayev (Experis)", 104)]
    [ProvideMenuResource("Menus.ctmenu", 1)]
    [ProvideToolWindow(typeof(MergeWIPane), MultiInstances = false, Style = VsDockStyle.Tabbed, PositionX = 0, PositionY = 0, Width = 900, Height = 750, Transient = false,
        Orientation = ToolWindowOrientation.Left, Window = "9DDABE98-1D02-11D3-89A1-00C04F688DDE")]
    [Guid(GuidList.guidExtendedMergePkgString)]
    public sealed class ExtendedMergePackage : Package
    {
        private EnvDTE.DTE dte = null;
        private TfsTeamProjectCollection tfscoll = null;
        private VersionControlServer vcsrv = null;
        private VersionControlExt vcext = null;
        private string AppTitle = null;
        private MergeWIControl mfrm;
        
        /// <summary>
        /// Default constructor of the package.
        /// Inside this method you can place any initialization code that does not require 
        /// any Visual Studio service because at this point the package object is created but 
        /// not sited yet inside Visual Studio environment. The place to do all the other 
        /// initialization is the Initialize method.
        /// </summary>
        public ExtendedMergePackage()
        {
            Trace.WriteLine(string.Format(CultureInfo.CurrentCulture, "Entering constructor for: {0}", this.ToString()));
        }

        /// <summary>
        /// Initializes the package variables.
        /// </summary>
        private void InitializeExtVariables()
        {
            dte = (EnvDTE.DTE)GetService(typeof(EnvDTE.DTE));
            //IVsUIShell uiShell = (IVsUIShell)GetService(typeof(SVsUIShell));

            var TFSExt = dte.GetObject("Microsoft.VisualStudio.TeamFoundation.TeamFoundationServerExt") as TeamFoundationServerExt;
            vcext = dte.GetObject("Microsoft.VisualStudio.TeamFoundation.VersionControl.VersionControlExt") as VersionControlExt;

            //tfscoll = new TfsTeamProjectCollection(new Uri(TFSExt.ActiveProjectContext.DomainUri));
            tfscoll = TfsTeamProjectCollectionFactory.GetTeamProjectCollection(new Uri(TFSExt.ActiveProjectContext.DomainUri));
            tfscoll.Connect(Microsoft.TeamFoundation.Framework.Common.ConnectOptions.None);
            tfscoll.EnsureAuthenticated();

            vcsrv = (VersionControlServer)tfscoll.GetService(typeof(VersionControlServer));

            AppTitle = Resources.ResourceManager.GetString("AppTitle");

            var outputWindow = GetService(typeof(SVsOutputWindow)) as IVsOutputWindow;
            var dialogFactory = GetService(typeof(SVsThreadedWaitDialogFactory)) as IVsThreadedWaitDialogFactory;

            DocumentService docsrv2 = (DocumentService)GetService(typeof(DocumentService));

            Utilities.dialogFactory = dialogFactory;
            Utilities.outputWindow = outputWindow;
            Utilities.AppTitle = AppTitle;

            MergeFactory.vcext = vcext;
            MergeFactory.vcsrv = vcsrv;
            MergeFactory.tfscoll = tfscoll;
            MergeFactory.docsrv2 = docsrv2;
            MergeFactory.dte = dte;
        }

        /////////////////////////////////////////////////////////////////////////////
        // Overriden Package Implementation
        #region Package Members

        /// <summary>
        /// Initialization of the package; this method is called right after the package is sited, so this is the place
        /// where you can put all the initilaization code that rely on services provided by VisualStudio.
        /// </summary>
        protected override void Initialize()
        {
            Trace.WriteLine (string.Format(CultureInfo.CurrentCulture, "Entering Initialize() of: {0}", this.ToString()));
            base.Initialize();

            // Add our command handlers for menu (commands must exist in the .vsct file)
            OleMenuCommandService mcs = GetService(typeof(IMenuCommandService)) as OleMenuCommandService;
            if ( null != mcs )
            {
                // Create the command for the menu item.
                CommandID menuCommandID = new CommandID(GuidList.guidExtendedMergeCmdSet, (int)PkgCmdIDList.MyWIExpMergeID);
                MenuCommand menuItem = new MenuCommand(MyWIExpMergeIDCallback, menuCommandID);
                mcs.AddCommand( menuItem );

                menuCommandID = new CommandID(GuidList.guidExtendedMergeCmdSet, (int)PkgCmdIDList.MySCQuMergeID);
                menuItem = new MenuCommand(MySCQuBuildIDCallback, menuCommandID);
                mcs.AddCommand(menuItem);
            }
        }
        #endregion

        /// <summary>
        /// Menu callback event handler that activates the merge pane after selecting paths in Source Control Explorer.
        /// See <see cref="MergeWIControl"/>
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
        private void MySCQuBuildIDCallback(object sender, EventArgs e)
        {
            InitializeExtVariables();
            var origCursor = Cursor.Current;
            Cursor.Current = Cursors.WaitCursor;

            /*if (dte.Properties["TFS Productivity Tools", "General"].Item("IDEPath").Value.ToString() == "")
            {
                Cursor.Current = origCursor;
                MessageBox.Show("The Visual Studio IDE Path parameter is not set.", AppTitle);
                return;
            }*/

            if (vcext.Explorer.Workspace.Folders.Length == 0)
            {
                Cursor.Current = origCursor;
                MessageBox.Show("The workspace is not mapped to any local folder.", AppTitle);
                return;
            }

            string folderPath = vcext.Explorer.CurrentFolderItem.SourceServerPath;
            if (!MergeFactory.ServerItemExists(folderPath))
            {
                Cursor.Current = origCursor;
                MessageBox.Show("Target server path is cloacked or doesn't exist.");
                return;
            }

            MergeWIPane pane = (MergeWIPane)this.FindToolWindow(typeof(MergeWIPane), 0, true);
            if (pane == null)
            {
                Cursor.Current = origCursor;
                throw new COMException("Failed to initialize " + AppTitle + ": Pane was null.");
            }

            mfrm = pane.control;
            mfrm.SuppressEvents = true;
            mfrm.defServerName = folderPath;
            mfrm.Initialize();
            mfrm.ClearGrids();

            IVsWindowFrame frame = pane.Frame as IVsWindowFrame;
            if (frame == null)
            {
                Cursor.Current = origCursor;
                throw new COMException("Failed to initialize " + AppTitle + ": Frame was null.");
            }

            // Bring the tool window to the front and give it focus
            ErrorHandler.ThrowOnFailure(frame.Show());

            bool nolinks = true;
            bool bcanceled = false;
            int icanceled;

            IVsThreadedWaitDialog2 dlg = Utilities.CreateThreadedWaitDialog("Collecting information about changesets", "Starting to process changests...", "status", 100);
            dlg.UpdateProgress("Collecting information about changesets", "Starting to process changesets...", "status", 0, 100, true, out bcanceled);

            foreach (string sourcePath in vcext.Explorer.SelectedItems.Select(x => x.SourceServerPath))
            {
                int idx = 0;
                foreach (Changeset ch in vcsrv.QueryHistory(sourcePath, VersionSpec.Latest, 0, RecursionType.Full, null, null, null,
                                                                Int32.MaxValue, true, true, false, false))
                {
                    if (ch.Changes == null || ch.Changes.Length == 0) continue;

                    string change = ch.Changes.Take(5).Select(x => x.ChangeType.ToString()).Aggregate((x, y) => x + "," + y);
                    if (ch.Changes.Length > 5) change += "...";

                    string wrkitemstr = "";
                    int wrkitemid = 0;
                    if (ch.WorkItems != null && ch.WorkItems.Length > 0)
                    {
                        wrkitemstr = ch.WorkItems.Select(x => x.Id.ToString()).Aggregate((x, y) => x + "," + y);
                        wrkitemid = ch.WorkItems[0].Id;
                    }

                    ListViewItem itm = mfrm.AddNewGridItem(wrkitemstr, ch.ChangesetId.ToString(),
                                            ch.CreationDate.ToShortDateString(), ch.Owner, sourcePath, change, ch.Comment,
                                            new ListViewItemTag(ch.ChangesetId, wrkitemid, sourcePath, ch.CreationDate));

                    Utilities.OutputCommandString("Adding Changeset: " + itm.Name + " => " + sourcePath);
                    nolinks = false;

                    dlg.UpdateProgress("Collecting information about changesets", "Processing changeset: " + ch.ChangesetId, "status", idx++, 100, false, out bcanceled);
                    if (bcanceled)
                    {
                        mfrm.SortItems();
                        mfrm.SuppressEvents = false;
                        Cursor.Current = origCursor;
                        return;
                    }
                    if (idx == 100) idx = 0;
                }
            }
            mfrm.SortItems();
            mfrm.UpdateRelatedBranchesCombo();
            mfrm.SuppressEvents = false;
            Cursor.Current = origCursor;
            ErrorHandler.ThrowOnFailure(dlg.EndWaitDialog(out icanceled));

            if (nolinks) MessageBox.Show("No Changesets where found in selected path.", AppTitle);

            return;
        }

        /// <summary>
        /// Menu callback event handler that activates the merge pane after selecting work items in Work Item Query view.
        /// See <see cref="MergeWIControl"/>
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
        private void MyWIExpMergeIDCallback(object sender, EventArgs e)
        {
            InitializeExtVariables();
            var origCursor = Cursor.Current;
            Cursor.Current = Cursors.WaitCursor;

            /*if (dte.Properties["TFS Productivity Tools", "General"].Item("IDEPath").Value.ToString() == "")
            {
                Cursor.Current = origCursor;
                MessageBox.Show("The Visual Studio IDE Path parameter is not set.", AppTitle);
                return;
            }*/

            DocumentService docsrv2 = (DocumentService)GetService(typeof(DocumentService));
            //IVsTeamExplorer vsTeamExp2 = (IVsTeamExplorer)GetService(typeof(IVsTeamExplorer));
            //Workspace wrkspc = vcext.Explorer.Workspace;

            var vsTeamExp2 = (ITeamFoundationContextManager)GetService(typeof(ITeamFoundationContextManager));

            MergeWIPane pane = (MergeWIPane)this.FindToolWindow(typeof(MergeWIPane), 0, true);
            if (pane == null)
            {
                Cursor.Current = origCursor;
                throw new COMException("Failed to initialize " + AppTitle + ": Pane was null.");
            }

            mfrm = pane.control;
            mfrm.SuppressEvents = true;
            mfrm.defServerName = "$/" + vsTeamExp2.CurrentContext.TeamProjectName;
            mfrm.Initialize();
            mfrm.ClearGrids();

            IVsWindowFrame frame = pane.Frame as IVsWindowFrame;
            if (frame == null)
            {
                Cursor.Current = origCursor;
                throw new COMException("Failed to initialize " + AppTitle + ": Frame was null.");
            }

            // Bring the tool window to the front and give it focus
            ErrorHandler.ThrowOnFailure(frame.Show());

            int idx = 0;
            int iddx = 0;
            bool nolinks = true;
            bool bcanceled = false;
            int icanceled;
            object _lockToken1 = new object();
            object _lockToken2 = new object();
            string sourcePath;

            IVsThreadedWaitDialog2 dlg = Utilities.CreateThreadedWaitDialog("Collecting information about changesets", "Starting to process changesets...", "status", 100);
            dlg.UpdateProgress("Collecting information about changesets", "Starting to process changesets...", "status", 0, 100, true, out bcanceled);

            IWorkItemTrackingDocument doc2 = docsrv2.FindDocument(dte.ActiveDocument.FullName, _lockToken2);
            if (doc2 == null)
            {
                Cursor.Current = origCursor;
                return;
            }

            foreach (int i in (doc2 as IResultsDocument).SelectedItemIds)
            {
                IWorkItemDocument widoc = docsrv2.GetWorkItem(tfscoll, i, _lockToken1);
                if (widoc == null) continue;

                if(!widoc.IsLoaded) widoc.Load();
                iddx = 0;
                while (!widoc.IsLoaded && ++iddx < 10)
                {
                    Thread.Sleep(1000);
                    if (widoc.ItemState == WorkItemState.Error)
                    {
                        Utilities.OutputCommandString(widoc.Exception != null ? widoc.Exception.ToString() : "WorkItem cannot be loaded "+i);
                        iddx = i;
                        break;
                    }
                }
                if (iddx == 10)
                {
                    Utilities.OutputCommandString("WorkItem cannot be loaded "+i+". Please open it manually and then try to load again.");
                    iddx = i;
                    break;
                }
                iddx = 0;

                foreach (Link lnk in widoc.Item.Links.Cast<Link>().Where(x => x.ArtifactLinkType.Name == "Fixed in Changeset"))
                {
                    Changeset ch = vcsrv.ArtifactProvider.GetChangeset(new Uri((lnk as ExternalLink).LinkedArtifactUri));
                    if (ch.Changes == null || ch.Changes.Length == 0) continue;

                    string change = ch.Changes.Take(5).Select(x => x.ChangeType.ToString()).Aggregate((x, y) => x + "," + y);
                    if (ch.Changes.Length > 5) change += "...";

                    string[] relbranches = MergeFactory.QueryBranchObjectOwnership(ch.ChangesetId);
                    if (relbranches.Length == 0)
                    {
                        sourcePath = "$/" + vsTeamExp2.CurrentContext.TeamProjectName;
                    }
                    else
                        sourcePath = relbranches[0];
                    //}

                    ListViewItem itm = mfrm.AddNewGridItem(widoc.Item.Id.ToString(), ch.ChangesetId.ToString(),
                                            ch.CreationDate.ToShortDateString(), ch.Owner, sourcePath, change, ch.Comment,
                                            new ListViewItemTag(ch.ChangesetId, widoc.Item.Id, sourcePath, ch.CreationDate));

                    Utilities.OutputCommandString("Adding Changeset: " + itm.Name + " => " + sourcePath);
                    nolinks = false;

                    dlg.UpdateProgress("Collecting information about changesets", "Processing changeset: " + ch.ChangesetId, "status", idx++, 100, false, out bcanceled);
                    if (bcanceled)
                    {
                        widoc.Release(_lockToken1);
                        doc2.Release(_lockToken2);
                        mfrm.SortItems();
                        mfrm.SuppressEvents = false;
                        Cursor.Current = origCursor;
                        return;
                    }
                    if (idx == 100) idx = 0;
                }
                widoc.Release(_lockToken1);
            }
            doc2.Release(_lockToken2);

            mfrm.SortItems();
            mfrm.UpdateRelatedBranchesCombo();
            mfrm.SuppressEvents = false;
            Cursor.Current = origCursor;
            ErrorHandler.ThrowOnFailure(dlg.EndWaitDialog(out icanceled));

            if (iddx > 0) MessageBox.Show("WorkItem cannot be loaded " + iddx + ". Please open it manually and then try to load again.", AppTitle);
                else if (nolinks) MessageBox.Show("No linked Changesets where found in selected Work Items.", AppTitle);
            
            return;
        }

    }
}
